Cobalt抢占式和优先级调度


Cobalt 抢占式和优先级调度

xkernel 项目是一个开源的 RTOS-to-Linux 可移植框架,遵循 Creative Commons BY-SA 3.0 和 GPLv2 许可证,有两种形式:

xkernel 项目将一个名为 Cobalt 核心的实时内核合并到了 Linux 内核中,并在内核空间中共存。Cobalt 核心中的中断和线程优先级比 Linux 内核中的中断和线程更高。由于 Cobalt 核心执行的指令较 Linux 内核少,因此可以减少调用路径中的不必要延迟和历史负担。

使用这种实时目标设计,xkernel修补的Linux内核可以实现良好的实时多线程性能。


双域中断管道 - [Head] 和 [Root]

两阶段中断管道是实现 Cobalt 实时框架的底层机制。

Cobalt 补丁 Linux 内核在硬件中断上占据主导地位,这些中断原本属于 Linux 内核。Cobalt 将首先处理它感兴趣的中断,然后将其他中断路由到 Linux 内核。前者称为 head stage,后者称为 root stage:

[Head] 阶段的优先级高于 [Root] 阶段,通过压缩硬件和软件的处理时间提供最短的响应时间。


设置POSIX 线程在双域切换

Linux 上,taskset 命令允许你更改进程的 CPU 亲和性。通常,它与内核命令行中确定的 CPU 隔离一起使用。以下示例脚本演示了将实时进程的亲和性更改为核心 1 的操作:

cpu="1"
taskset -c $cpu latency -c1 -t0 -p 100 -P 99 -h -g result.txt

如果作为systemd服务作为系统守护进程,可以参考下面的设置:

[Unit]
Description=cobalt latency service
Wants=network-online.target
[Service]
CPUAffinity=1
ExecStart=/usr/local/bin/latency -c1 -t0 -p 100 -P 99 -h -g result.txt
Type=exec
[Install]
WantedBy=multi-user.target
Alias=latency.service

Cobalt 核心的线程并不是完全与 Linux 内核的线程隔离。相反,它重用了普通的 kthread 并添加了特殊功能;kthread 可以在 Cobalt 的实时上下文(out-of-band 实时域)和普通的 Linux 内核上下文(in-band 非实时域)之间切换。其优势在于,当处于实时域上下文时,线程可以利用 Linux 内核的基础设施。在典型场景中,Cobalt 线程会以普通 kthread 的形式启动,调用 Linux 内核的 API 进行准备工作,然后切换到实时域上下文,作为 Cobalt 线程执行实时工作。其劣势在于,处于实时域上下文时,Cobalt 线程很容易因为误调用 Linux 内核 API 而迁移到非实时上下文。在这种情况下,问题很难被发现;开发者通常误认为他们的线程正在 Cobalt 核心下运行,直到检查 ftrace 输出或任务超出其截止期限时才注意到问题。

基于 Cobalt 的用户空间应用程序可以在抢占式调度和通用分时(SCHED_OTHER)调度策略之间影射同一线程:

非实时域:此模式下可以访问 Linux GPOS 服务和 Linux [ROOT] 域设备驱动程序(例如,可以使用 ps -x 或 top 等 Linux 命令)。 实时域:此模式下可以访问所有 xkernel RTOS 服务和 RTDM [HEAD] 域设备驱动程序。

可以通过以下命令查看进程状态:

cat /proc/xenomai/sched/stat

使用Cobalt POSIX API 封装

当使用 libcobalt.so 链接时,通过 pthread_create()pthread_setschedparam() 设置的 Linux pthread,任何对标准(S) POSIX 调度策略的更改,例如 SCHED_FIFOSCHED_RR,都会通过一种称为影射 (shadowing) 的机制,跳转到 Cobalt 任务。

此外,xkernel/Cobalt 提供了补充的调度策略(X)

Scheduling Policies 基础Linux 基础Linux + PREEMPT_RT 基础Linux + xkernel/COBALT
SCHED_TP, SCHED_BATCH, SCHED_IDLE S S S
SCHED_FIFO, SCHED_RR N N N
SCHED_FIFO N P P
SCHED_TP, SCHED_SPORADIC, and SCHED_QUOTA - - X

注释:


在Cobalt中设置高分辨率计时器线程

MetaOS 支持使用X86 硬件定时器,以基于高优先级 实时任务中断创建时间间隔:

通过下面的命令查看Cobalt计时器信息:

cat /proc/xenomai/timer/coreclk

深入解析 Cobalt 的补充调度算法

在实时系统开发中,调度算法的选择和应用对系统的性能和实时性至关重要。xkernelCobalt 实时内核为开发者提供了多种调度策略,以满足不同应用场景的需求。将深入探讨 Cobalt 的补充调度算法,包括 SCHED_QUOTASCHED_TPSCHED_WEAKSCHED_SPORADIC,并详细解释它们的工作原理、应用场景以及如何在实际开发中有效地利用这些策略。

在实时系统中,任务的调度需要考虑到多种因素,如任务的优先级、执行时间、资源使用等。传统的调度策略,如 SCHED_FIFOSCHED_RR,在某些场景下可能无法满足复杂的调度需求。为此,Cobalt 提供了多种补充调度策略,帮助开发者更灵活地管理系统中的线程和资源。

SCHED_QUOTA 调度策略

SCHED_QUOTA 是一种基于配额(Quota)的调度策略,它通过限制线程在固定的时间周期内的 CPU 使用量,来实现对线程组的资源控制。在该策略下,线程被分配到不同的线程组(Thread Group),每个组在全局配额周期内被分配一定的运行时间配额(以百分比表示)。在组内,所有线程遵循 SCHED_FIFO 策略。

工作原理

全局配额周期(Global Quota Period):这是一个固定的时间周期,例如 1 秒(默认),整个系统的配额管理都基于这个周期。

线程组配额:每个线程组被分配一定比例的运行时间,例如 35%、25% 等。

线程执行

示例说明

假设系统中有 5 个线程组,每个组的配额和全局周期如下:

SCHED_QUOTA 调度策略示例图

在每个全局周期内,各线程组可以在其配额内运行线程。当组内所有线程的总运行时间达到配额后,该组将被暂停,等待下一个周期。

运行时间预算和峰值配额

运行时间预算:在每个全局周期开始时,线程组获得其完整的配额。如果该组在当前周期未完全消耗其配额,剩余的预算会被累积到下一个周期,直到达到定义的峰值配额(Peak Quota)。

峰值配额:这是线程组可累积的最大配额。当累积的预算超过峰值配额时,超出的部分会在后续多个周期内逐步消耗。

应用场景

SCHED_TP 调度策略

SCHED_TP 策略实现了一种称为**时间分区(Temporal Partitioning)**的机制,它通过将 CPU 时间划分为固定的时间窗口,确保不同线程组在时间上不发生重叠地执行。

工作原理

主时间帧(Global Time Frame):一个固定的、重复的全局时间周期。

次要帧(Secondary Frames):主时间帧被划分为多个次要帧(时间窗口),每个次要帧具有固定的持续时间和相对于主时间帧的偏移量。

分区(Partitions):每个次要帧分配给一个特定的线程组(分区)。在次要帧的时间窗口内,只有该分区内的线程被允许执行。

线程执行

示例说明

假设主时间帧为 100 毫秒,被划分为 5 个次要帧:

在每个次要帧内,只有对应分区内的线程被调度执行。这样可以确保不同分区的线程在时间上严格隔离,防止相互干扰。

SCHED_TP 调度策略示例图

应用场景

SCHED_WEAK 调度策略

SCHED_WEAK 策略用于实现弱调度(Weak Scheduling),其主要目的是让线程能够与实时线程进行同步,但不与实时线程竞争 CPU 资源。

工作原理

应用场景

SCHED_SPORADIC 调度策略

SCHED_SPORADIC零星调度策略通常用于对给定时间段内的线程执行时间提供上限。在零星调度下,线程的优先级可以在前台(正常优先级)与后台(低优先级)之间动态振荡。与 FIFO 调度一样,使用零星调度的线程会继续执行,直到它被更高优先级的线程阻塞或抢占。与自适应调度一样,使用零星调度的线程的优先级会降低,但使用零星调度可以更精确地控制线程的行为。

零星调度策略支持的不规则条件:

初始预算(C):线程在被降级为低优先级 (L) 之前被允许以正常优先级 (N) 执行的时间量。

低优先级(L):线程将降级到的优先级。线程在后台时以这个较低的优先级 (L) 执行,在前台时以正常优先级 (N) 运行。

补充期(T):线程被允许消耗其执行预算的时间段。为了安排补充操作,POSIX 实现还使用此值作为线程变为 READY 的时间偏移量。

待处理补充的最大数量:此值限制可以进行的补充操作的数量,从而限制不规则调度策略消耗的系统开销。

执行过程

  1. 初始运行:线程以正常优先级(N)开始执行,预算为 10 毫秒。
  2. 预算消耗:线程运行 3 毫秒后被阻塞,剩余预算为 7 毫秒。
  3. 唤醒继续:线程在 6 毫秒时被唤醒,继续以正常优先级运行,消耗剩余的 7 毫秒预算。
  4. 预算耗尽:预算耗尽后,线程的优先级降低为低优先级(L)。
  5. 补充预算:在补充期(T)结束后,线程的预算被补充,优先级恢复为正常优先级(N)。
  6. 循环执行:线程继续在两个优先级之间切换,按照预算和补充期的设定执行。

示例图解

SCHED_SPORADIC 调度策略示例图

时间轴:

这样线程将继续在其两个优先级之间振荡,以一种受控的、可预测的方式为系统中的非周期性事件提供服务。

应用场景